import wx
from numpy import size, matrix
import wx.lib.mixins.listctrl
import os, sys

import winsound

##  Bony landmarks are measured in this panel.
#   The Bony Landmark panel consists of a list with bony landmarks, the five measurements and the description of the selected bony landmark.
class BLPanel(wx.Panel):

    ## The constructor of BLPanel, sets up the panel interface.
    #   @param[in]  parent  The wx window parent (notebook) of this panel.
    #   @param[in]  state   BLState instance attached to this panel.
    def __init__(self, parent, state):
        wx.Panel.__init__(self, parent)
        ##  BLState instance attached to this panel.
        self.state = state
        ##  The wx window parent (notebook) of this panel.
        self.parent = parent
        ##  The name of this notebook panel.
        self.name = "Bony Landmarks"
        ##  Box containing the selected bony landmark.
        self.selectedItemSizer_staticbox = wx.StaticBox(self, -1, "Selected")
        ##  Box with indications for the five measurements.
        self.measurementsSizer_staticbox = wx.StaticBox(self, -1, "Measurements")
        ##  Help text.
        self.blHelpSizer_staticbox = wx.StaticBox(self, -1, "Help")
        ##  List containing the bony landmarks and reset buttons.
        self.blListSizer_staticbox = wx.StaticBox(self, -1, "Protocol")
        ##  List for the bony landmarks.
        self.blList = BlList(self, -1, style=wx.LC_REPORT|wx.LC_NO_HEADER|wx.LC_SINGLE_SEL|wx.SUNKEN_BORDER)
    
        ##  Resets the selected bony landmark.
        self.resetSelectedButton = wx.Button(self, -1, "Reset selected")
        ##  Resets all bony landmarks.
        self.resetAllButton = wx.Button(self, -1, "Reset all")
        ##  The selected bony landmark name.
        self.selectedItemLabel = wx.StaticText(self, -1, "Bony Landmark Name")
        ##  The selected bony landmark picture.
        self.selectedItemBitmap = wx.StaticBitmap(self, -1, wx.Bitmap("Images/BonyLandmarkImages/BonyLandmark 0.png", wx.BITMAP_TYPE_ANY))
        ##  The selected bony landmark description.
        self.selectedItemDescription = wx.StaticText(self, -1, "Bony Landmark description")

        measure1 = wx.StaticBitmap(self, -1, wx.Bitmap("Images/1.png", wx.BITMAP_TYPE_ANY))
        measure2 = wx.StaticBitmap(self, -1, wx.Bitmap("Images/2.png", wx.BITMAP_TYPE_ANY))
        measure3 = wx.StaticBitmap(self, -1, wx.Bitmap("Images/3.png", wx.BITMAP_TYPE_ANY))
        measure4 = wx.StaticBitmap(self, -1, wx.Bitmap("Images/4.png", wx.BITMAP_TYPE_ANY))
        measure5 = wx.StaticBitmap(self, -1, wx.Bitmap("Images/5.png", wx.BITMAP_TYPE_ANY))
        ##  Indicators for the five measurements.
        self.measureBitmaps = [measure1, measure2, measure3, measure4, measure5]

        helptxt = "Place the pointer sensor on the correct position on the patient's body," \
                  "and step on the pedal five times. Repeat this process for all the bony landmarks.\n\n " \
                  "A green indicator is a correct measurement, while orange indicates that the measurement " \
                  "is too far from other current measurements, or that the bony landmark " \
                  "is positioned wrong with respect to other bony landmarks.\n" \
                  "To repeat a measurement, use the reset buttons located below the list to clear the current measurements."

        ##  Button for making a measurement.
        self.pedalButton = wx.Button(self, -1, "Pedal")
        self.pedalButton.Enable(False)
        self.panel_7 = wx.Panel(self, -1)
        self.helpLabel = wx.StaticText(self, -1, helptxt)
        self.static_line_5_copy = wx.StaticLine(self, -1)
        self.static_line_6_copy = wx.StaticLine(self, -1)
        self.static_line_7_copy = wx.StaticLine(self, -1)
        self.panel_3_copy = wx.Panel(self, -1)
        self.panel_1 = wx.Panel(self, -1)
        ##  Goes to the previous panel.
        self.blBackButton = wx.Button(self, -1, "< Back")
        ##  Goes to the next panel.
        self.blNextButton = wx.Button(self, -1, "Next >")

        self.__AssignBonyLandmarkList()
        self.__SetProperties()
        self.__BindEvents()
        self.__DoLayout()

    ##  Sets properties for the panel.
    def __SetProperties(self):
        self.blList.SetMinSize((200, -1))
        self.selectedItemLabel.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "MS Shell Dlg 2"))
        self.selectedItemDescription.SetMinSize((160, 200))
        self.helpLabel.SetMinSize((150, 254))

    ##  Sets layout for the panel.
    def __DoLayout(self):
        blWrapper = wx.FlexGridSizer(4, 3, 0, 0)
        backNextGridSizer = wx.FlexGridSizer(1, 2, 0, 10)
        blHelpSizer = wx.StaticBoxSizer(self.blHelpSizer_staticbox, wx.HORIZONTAL)
        selectedItemMeasurementsSizer = wx.FlexGridSizer(3, 1, 20, 0)
        measurementsSizer = wx.StaticBoxSizer(self.measurementsSizer_staticbox, wx.HORIZONTAL)
        measurementsGridSizer = wx.GridSizer(6, 1, 5, 0)
        selectedItemSizer = wx.StaticBoxSizer(self.selectedItemSizer_staticbox, wx.HORIZONTAL)
        selectedItemWrapper = wx.BoxSizer(wx.VERTICAL)
        selectedItemImageDescSizer = wx.BoxSizer(wx.HORIZONTAL)
        blListSizer = wx.StaticBoxSizer(self.blListSizer_staticbox, wx.HORIZONTAL)
        blListGridSizer = wx.FlexGridSizer(2, 1, 5, 0)
        blListGridSizer.Add(self.blList, 1, wx.EXPAND, 0)
        blListGridSizer.Add(self.resetSelectedButton, 0, wx.EXPAND|wx.ALIGN_BOTTOM, 0)
        blListGridSizer.Add(self.resetAllButton, 0, wx.EXPAND, 0)
        blListGridSizer.AddGrowableRow(0)
        blListSizer.Add(blListGridSizer, 1, wx.ALL|wx.EXPAND, 10)
        blWrapper.Add(blListSizer, 1, wx.ALL|wx.EXPAND, 10)
        selectedItemWrapper.Add(self.selectedItemLabel, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 10)
        selectedItemImageDescSizer.Add(self.selectedItemBitmap, 0, wx.LEFT|wx.BOTTOM, 10)
        selectedItemImageDescSizer.Add(self.selectedItemDescription, 0, wx.LEFT|wx.RIGHT|wx.BOTTOM, 10)
        selectedItemWrapper.Add(selectedItemImageDescSizer, 1, wx.EXPAND, 0)
        selectedItemSizer.Add(selectedItemWrapper, 1, wx.EXPAND, 0)
        selectedItemMeasurementsSizer.Add(selectedItemSizer, 1, wx.EXPAND, 10)

        for bitmap in self.measureBitmaps:
            measurementsGridSizer.Add(bitmap, 0, 0, 0)

        measurementsGridSizer.Add(self.pedalButton, 0, 0, 0)
        measurementsSizer.Add(measurementsGridSizer, 1, wx.ALL, 10)
        selectedItemMeasurementsSizer.Add(measurementsSizer, 1, 0, 10)
        selectedItemMeasurementsSizer.Add(self.panel_7, 1, wx.EXPAND, 0)
        selectedItemMeasurementsSizer.AddGrowableRow(1)
        selectedItemMeasurementsSizer.AddGrowableCol(0)
        blWrapper.Add(selectedItemMeasurementsSizer, 1, wx.ALL|wx.EXPAND, 10)
        blHelpSizer.Add(self.helpLabel, 0, wx.ALL, 10)
        blWrapper.Add(blHelpSizer, 1, wx.ALL|wx.ALIGN_RIGHT, 10)
        blWrapper.Add(self.static_line_5_copy, 0, wx.EXPAND, 0)
        blWrapper.Add(self.static_line_6_copy, 0, wx.EXPAND, 0)
        blWrapper.Add(self.static_line_7_copy, 0, wx.EXPAND, 0)
        blWrapper.Add(self.panel_3_copy, 1, wx.EXPAND, 0)
        blWrapper.Add(self.panel_1, 1, wx.EXPAND, 0)
        backNextGridSizer.Add(self.blBackButton, 0, wx.ALIGN_RIGHT, 0)
        backNextGridSizer.Add(self.blNextButton, 0, wx.RIGHT|wx.ALIGN_RIGHT, 10)
        blWrapper.Add(backNextGridSizer, 1, wx.TOP|wx.BOTTOM|wx.ALIGN_RIGHT|wx.ALIGN_BOTTOM, 5)
        self.SetSizer(blWrapper)
        blWrapper.AddGrowableRow(0)
        blWrapper.AddGrowableCol(1)
        #self.blList.Select(0)
        self.Layout()

    ##  Binds items of the panel to event handlers
    def __BindEvents(self):
        self.Bind(wx.EVT_BUTTON, self.OnPedal, self.pedalButton)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self.blList)
        self.Bind(wx.EVT_BUTTON, self.OnResetSelected, self.resetSelectedButton)
        self.Bind(wx.EVT_BUTTON, self.OnResetAll, self.resetAllButton)
        self.Bind(wx.EVT_BUTTON, self.OnBack, self.blBackButton)
        self.Bind(wx.EVT_BUTTON, self.OnNext, self.blNextButton)
        
        #~ self.Unbind(wx.EVT_RIGHT_DOWN)
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnPedal)

    ##  Checks the bony landmark measurement and colours the indicator.
    def OnPedal(self, event=0):
        if hasattr(sys, 'frozen') and sys.frozen:
            appdir, exe = os.path.split(sys.executable)
        else:
            dirname = os.path.dirname(sys.argv[0])
            if dirname and dirname != os.curdir:
                appdir = dirname
            else:
                appdir = os.getcwd()
                
        if self.pedalButton.Enabled == True:
            selectedBL = self.blList.GetFirstSelected()
            numberOfMeasurements = len(self.state.main.driver.imSensors[selectedBL])

            if numberOfMeasurements == 5:
                winsound.PlaySound(os.path.join(appdir, "Sound", "attention.wav"), winsound.SND_ALIAS)
                msg = "You already have 5 measurements for this bony landmark.\nClick 'Reset Selected' to remeasure this landmark."
                dlg = wx.MessageDialog(self, msg, 'Error', wx.OK | wx.ICON_ERROR)
                dlg.ShowModal()
                dlg.Destroy()
            else:
                self.state.BL_CHECKED = False
                self.state.main.driver.FetchBL_SensorPositionRotation(selectedBL,\
                                            self.state.main.bonyLandmarks[selectedBL].sensorID)
                self.state.CheckSelf(selectedBL)
                self.UpdateMeasureBitmaps(selectedBL)
                winsound.PlaySound(os.path.join(appdir, "Sound", "pointacquired.wav"), winsound.SND_ALIAS)

            if numberOfMeasurements == 4:
                winsound.PlaySound(os.path.join(appdir, "Sound", "Sound/bingheigh.wav"), winsound.SND_ALIAS)
                self.blList.Select(selectedBL + 1)
                self.state.setRelativeBL(selectedBL)

    ##  Displays name, description, picture and measurement indicators of the selected bony landmark when it is selected in the bony landmark list.
    def OnItemSelected(self, event):        
        self.pedalButton.Enable(True)
        
        selectedIndex = event.m_itemIndex
        
        # Get the bony landmark.
        selectedBL = self.state.main.bonyLandmarks[selectedIndex]
        self.selectedItemLabel.SetLabel(selectedBL.name)
        self.selectedItemDescription.SetLabel(self.state.descriptions[selectedBL.ID])

        # Do a check for the existence of the bitmap, displays a blank image if no corresponding image is available.
        path = os.path.join(self.state.main.config.root, "Images", "BonyLandmarkImages", ("BonyLandmark %d.png" %selectedBL.ID))
        if os.path.exists(path):
            self.selectedItemBitmap.SetBitmap(wx.Bitmap(path, wx.BITMAP_TYPE_ANY))
        else:
            self.selectedItemBitmap.SetBitmap(wx.Bitmap(self.state.main.config.root+"\\Images\\BonyLandmarkImages\\BonyLandmarkNoImage.png", wx.BITMAP_TYPE_ANY))

        self.Layout()
        self.UpdateMeasureBitmaps(selectedIndex)

    ##  Resets the measurements of the selected bony landmark when the reset button is clicked.
    def OnResetSelected(self, event):
        msg = "Are you sure you want to reset the %s?" % self.bonyLandmarks[self.blList.GetFirstSelected()]
        dlg = wx.MessageDialog(self, msg, 'Warning', wx.YES_NO | wx.NO_DEFAULT | wx.ICON_EXCLAMATION)
        if(dlg.ShowModal() == wx.ID_YES):
            self.state.ResetBL(self.blList.GetFirstSelected())
            self.UpdateMeasureBitmaps(self.blList.GetFirstSelected())
        dlg.Destroy()

    ##  Resets all bony landmark measurements when the reset all button is clicked.
    def OnResetAll(self, event):
        msg = "Are you sure you want to reset all bony landmarks?"
        dlg = wx.MessageDialog(self, msg, 'Warning', wx.YES_NO | wx.NO_DEFAULT | wx.ICON_EXCLAMATION)
        if(dlg.ShowModal() == wx.ID_YES):
            for i in range(self.blList.GetItemCount()):
                self.state.ResetBL(i)
            #Reset the bony landmarks which represent the GH-joints, in case they have already been calculated.
            #These are always the last bl's in the bonyLandmarks list
            for i in range(1,len(self.state.main.GHJoints)+1):
                self.state.main.bonyLandmarks[len(self.state.main.bonyLandmarks)-i].relativePosition = matrix("[0.0; 0.0; 0.0]")
            self.blList.Select(0)
            self.UpdateMeasureBitmaps(0)
        dlg.Destroy()

    ##  Panel navigation, goes back in the panel navigation to the previous state.
    #   @param[in]  new The tab index to go to.
    def OnBack(self, event, new = 0):
        self.parent.ChangeSelection(new)

    ##  Handles events when the next button is pressed.
    #   @param[in]  new     The tab index to go to.
    #   @return[boolean]    False when navigation to following panel is somehow cancelled, else True.
    def OnNext(self, event, new=2):
        NAVIGATED_TO_NEXT = False

        

        #Checks whether the measurements for the bony landmark are correct, if false a dialogue asking to proceed is shown.
        if self.state.CheckBonyLandmarks() == False:

            msg = "Some bony landmarks appear to have been measured wrongfully or have not been measured.\nAre you sure you want to continue?"
            dlg = wx.MessageDialog(self, msg, 'Warning', wx.YES_NO | wx.NO_DEFAULT | wx.ICON_EXCLAMATION)
            self.UpdateMeasureBitmaps(self.blList.GetFirstSelected())
            
            # If yes is chosen in dialog, set the new page.
            if(dlg.ShowModal() == wx.ID_YES):
                self.state.BL_CHECKED = True
                self.parent.ChangeSelection(new)
                #self.state.main.driver.fobbie.mode = 1
                NAVIGATED_TO_NEXT = True
                try:
                    self.state.main.driver.closeBLFile()
                except AttributeError:
                    pass
            # Otherwise, stay on the same page.
            else:
                self.parent.ChangeSelection(1)
                self.state.BL_CHECKED = False
            dlg.Destroy()
        # Sensors have been place correctly, navigate to new page.
        else:
            self.state.StoreRawBLVecs()
            self.parent.ChangeSelection(new)
            #self.state.main.driver.fobbie.mode = 1
            NAVIGATED_TO_NEXT = True

        return NAVIGATED_TO_NEXT

    ##  Update the bitmaps of the measurements with their colour represented in the colours matrix.
    #   @param[in]  selectedBL  The bony landmark for which the measurement indicators need to be updated.
    def UpdateMeasureBitmaps(self, selectedBL):
        # Update measurements of selected bony landmark.        
        for i in range(0,5):
            if i < len(self.state.colours[selectedBL]):
                if self.state.colours[selectedBL][i] == True:
                    path = os.path.join(self.state.main.config.root, "Images", ("%d+.png" % (i+1)))
                    #self.measureBitmaps[i].SetBitmap(wx.Bitmap(self.state.main.config.root+"\\Images\\%d+.png" % (i+1), wx.BITMAP_TYPE_ANY))
                else:
                    path = os.path.join(self.state.main.config.root, "Images", ("%d-.png" % (i+1)))
                    #self.measureBitmaps[i].SetBitmap(wx.Bitmap(self.state.main.config.root+"\\Images\\%d-.png" % (i+1), wx.BITMAP_TYPE_ANY))
            elif i == len(self.state.colours[selectedBL]):
                path = os.path.join(self.state.main.config.root, "Images", ("%ds.png" % (i+1)))
                #self.measureBitmaps[i].SetBitmap(wx.Bitmap(self.state.main.config.root+"\\Images\\%ds.png" % (i+1), wx.BITMAP_TYPE_ANY))
            else:
                path = os.path.join(self.state.main.config.root, "Images", ("%d.png" % (i+1)))
                #self.measureBitmaps[i].SetBitmap(wx.Bitmap(self.state.main.config.root+"\\Images\\%d.png" % (i+1), wx.BITMAP_TYPE_ANY))
            self.measureBitmaps[i].SetBitmap(wx.Bitmap(path, wx.BITMAP_TYPE_ANY))

        # Update BL List.
        for i in range(len(self.state.colours)):
            item = self.blList.GetItem(i,0)

            if len(self.state.colours[i]) == 5:
                if self.state.colours[i].__contains__(False):
                    item.SetBackgroundColour(wx.Colour(232,100,27))
                else:
                    item.SetBackgroundColour(wx.Colour(30,170,57))

                item.SetTextColour(wx.Colour(255,255,255))
            else:
                item.SetBackgroundColour(wx.WHITE)
                item.SetTextColour(wx.BLACK)
            self.blList.SetItem(item)

    ##  Assigns the items in the ListCtrl blList according to the configuration file.
    def __AssignBonyLandmarkList(self):
        self.blList.InsertColumn(0, "")
        ##  The list of bony landmarks.
        self.bonyLandmarks = self.state.GetBonyLandmarkList()

        for b in range(len(self.bonyLandmarks)-1,-1,-1):
            self.blList.InsertStringItem(0, self.bonyLandmarks[b])

##  List for the bony landmarks.
#   Here, wx.ListCtrl is used with ListCtrlAutoWidthMixin to create fitting columns.
class BlList(wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin):
    ##  The constructor of BlList.
    def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
        wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
        wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self)